package org.melanesia.converters.java;

import java.util.ArrayList;
import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;

import org.melanesia.converters.JavaToJavaConverter;
import org.melanesia.converters.TypeConverter;
import org.melanesia.converters.exceptions.ConversionException;
import org.melanesia.converters.exceptions.UnsupportedClassException;

/**
 * Converts java date class into another date class.
 *
 * @author marcin.kielar
 */
public class DateToDateConverter extends JavaToJavaConverter {

    /** The class that this converter converts to. */
    private final Class<?> toClass;

    /** Supported classes. */
    private static final Class<?>[] DATE_CLASSES = new Class<?>[] {
            java.util.Date.class, java.sql.Date.class,
            java.sql.Timestamp.class,
            javax.xml.datatype.XMLGregorianCalendar.class
    };

    /**
     * Constructs the DateToDateConverter.
     *
     * @param toClass
     *            class this converter converts values to
     */
    public DateToDateConverter(final Class<?> toClass) {
        this.toClass = toClass;
    }

    /**
     * Returns an array of array of classes. The returned structure contains a
     * matrix of possible conversions.
     *
     * @return matrix of possible conversions.
     */
    public static Class<?>[][] getSupportedConversions() {

        ArrayList<Class<?>[]> list = new ArrayList<Class<?>[]>();

        for (Class<?> dateClass1 : DATE_CLASSES) {
            for (Class<?> dateClass2 : DATE_CLASSES) {
                if (!dateClass1.equals(dateClass2)) {
                    list.add(new Class<?>[] { dateClass1, dateClass2 });
                }
            }
        }

        Class<?>[][] array = new Class<?>[list.size()][];
        list.toArray(array);

        return array;
    }

    @Override
    public final Object convert(final TypeConverter typeConverterContext, final Object value) throws ConversionException {

        if (value == null) {
            return null;
        } else if (toClass.equals(value.getClass())) {
            return value;
        } else {

            GregorianCalendar calendar = convertToGregorianCalendar(value);

            if (java.util.Date.class.equals(toClass)) {
                return new java.util.Date(calendar.getTimeInMillis());
            } else if (java.sql.Date.class.equals(toClass)) {
                return new java.sql.Date(calendar.getTimeInMillis());
            } else if (java.sql.Timestamp.class.equals(toClass)) {
                return new java.sql.Timestamp(calendar.getTimeInMillis());
            } else if (javax.xml.datatype.XMLGregorianCalendar.class.equals(toClass)) {
                try {
                    return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar);
                } catch (DatatypeConfigurationException e) {
                    throw new ConversionException("Error instantiating XMLGregorianCalendar");
                }
            }
        }

        throw new UnsupportedClassException(toClass);
    }

    /**
     * Converts value into a GregorianCalendar.
     *
     * @param value
     *            value to convert
     * @return GregorianCalendar representation of the value
     * @throws ConversionException
     *             if the value is of unsupported type
     */
    private GregorianCalendar convertToGregorianCalendar(final Object value) throws ConversionException {

        GregorianCalendar calendar = null;

        if (value instanceof java.util.Date) {
            calendar = new GregorianCalendar();
            calendar.setTimeInMillis(((java.util.Date) value).getTime());
        } else if (value instanceof java.sql.Date) {
            calendar = new GregorianCalendar();
            calendar.setTimeInMillis(((java.sql.Date) value).getTime());
        } else if (value instanceof java.sql.Timestamp) {
            calendar = new GregorianCalendar();
            calendar.setTimeInMillis(((java.sql.Timestamp) value).getTime());
        } else if (value instanceof javax.xml.datatype.XMLGregorianCalendar) {
            calendar = ((javax.xml.datatype.XMLGregorianCalendar) value).toGregorianCalendar();
        } else {
            throw new UnsupportedClassException(value.getClass());
        }

        return calendar;
    }
}
